import hashlib
import hmac
import traceback
import uuid

from models import user
from models.user import get_sms_code
from views import BaseHandler, SMSHandler
from configs import errorCode, errorMsg, config, const
from utils import util


class UserLoginThirdParty(BaseHandler):
    def get(self):
        self.write_json(["http://www.google.com", "http://www.baidu.com"])


class SMSRegister(SMSHandler):
    async def get(self, mobile):
        """
        ---
        tags:
          - User
        summary: 短信注册 发送验证码
        description: 发送验证码到path中的手机号
        parameters:
          - name: mobile
            in: path
            description: 手机号
            required: true
            schema:
              type: string
        responses:
          '200':
            description: successful operation
            content:
              application/json:
                schema:
                  $ref: '#/components/schemas/OkModel'
          '400':
            description: error operation
            content:
              application/json:
                schema:
                  $ref: '#/components/schemas/ErrorModel'
        """
        rows, res = await user.get_user_by_mobile(mobile)
        if rows > 0:
            return self.write_json(status=errorCode.MOBILE_DUPLICATED, data=errorMsg.MOBILE_DUPLICATED_MSG)
        else:
            return await self.send_msg(mobile, const.redis_prefix_mall_register)

    async def post(self, mobile):
        """
        ---
        tags:
          - User
        summary: 短信注册，提交用户信息
        description: 接受上一步的短信验证码，用户名，密码，注册，注册成功返回cookie设置保持登录
        parameters:
          - name: mobile
            in: path
            description: 手机号
            required: true
            schema:
              type: string
        requestBody:
          description: Post object that needs to be added to the blog
          content:
            application/x-www-form-urlencoded:
                schema:
                  type: object
                  properties:
                    sms_code:          # <!--- form field name
                      type: string
                    username:
                        type: string
                    passwd1:
                        type: string
                    passwd2:
                        type: string
                  required:
                    - sms_code
                    - username
                    - passwd1
                    - passwd2
            multipart/form-data:
                schema:
                  type: object
                  properties:
                    sms_code:          # <!--- form field name
                      type: string
                    username:
                        type: string
                    passwd1:
                        type: string
                    passwd2:
                        type: string
                  required:
                    - sms_code
                    - username
                    - passwd1
                    - passwd2
        responses:
          '200':
            description: successful operation
            content:
              application/json:
                schema:
                  $ref: '#/components/schemas/OkModel'
          '400':
            description: error operation
            content:
              application/json:
                schema:
                  $ref: '#/components/schemas/ErrorModel'
        """
        _, passwd1, passwd2, sms_code = self.get_username_password_from_body()
        if not all((passwd1, passwd2, sms_code)):
            return self.write_error(errorCode.INVALID_ARGS, errorMsg.INVALID_ARGS_MSG, status_code=200)

        if passwd1 != passwd2:
            return self.write_error(errorCode.REGISTER_PASSWORD_NOT_EQUAL, errorMsg.REGISTER_PASSWORD_NOT_EQUAL_MSG,
                                    status_code=200)

        cache_sms_code = await get_sms_code(mobile, const.redis_prefix_mall_register)
        if cache_sms_code is None:
            return self.write_error(errorCode.SMS_CODE_EXPIRED, errorMsg.SMS_CODE_EXPIRED_MSG, status_code=200)

        if sms_code != cache_sms_code:
            return self.write_error(errorCode.SMS_CODE_ERR, errorMsg.SMS_CODE_ERR_MSG, status_code=200)

        # rows, res = await user.get_id_by_mobile(mobile)
        # if rows != 0:
        #     return self.write_error(errorCode.USERNAME_DUPLICATED, errorMsg.USERNAME_DUPLICATED_MSG, status_code=200)

        rows = await user.insert_user_by_mobile(mobile, passwd1, mobile)

        if rows == 1:
            rows, res = await user.get_user_id_by_mobile(mobile)
            if rows > 0:
                uid = res[0].get('id')
                session = self.save_to_session(uid)
                return self.write_json(kwargs=session)
        return self.write_error(errorCode.DB_ERR, errorMsg.DB_ERR_MSG, status_code=200)

    def get_username_password_from_body(self):
        tmp = util.json_decode(self.request.body)
        username = tmp.get('username', None)
        passwd1 = tmp.get('passwd1', None)
        passwd2 = tmp.get('passwd2', None)
        sms_code = tmp.get('sms_code', None)
        return username, passwd1, passwd2, sms_code


class SMSLogin(SMSHandler):
    async def get(self, mobile):
        """
        ---
        tags:
          - User
        summary: 手机短信登录 请求发送短信验证码
        description: 未注册 （204 无内容）-> 跳转注册页面 sms_register/{mobile}
        parameters:
          - name: mobile
            in: path
            description: 手机号
            required: true
            schema:
              type: string

        responses:
          '200':
            description: successful operation
            content:
              application/json:
                schema:
                  $ref: '#/components/schemas/OkModel'
          '204':
            description: user not exist redirect to sms_register/{mobile}
            content:
              application/json:
                schema:
                  $ref: '#/components/schemas/OkModel'
          '400':
            description: error operation
            content:
              application/json:
                schema:
                  $ref: '#/components/schemas/ErrorModel'
        """

        rows, res = await user.get_user_by_mobile(mobile)
        if rows == 0:
            return self.write_error(errorCode.MOBILE_NOT_EXIST, errorMsg.MOBILE_NOT_EXIST_MSG, status_code=200)
        return await self.send_msg(mobile)

    async def post(self, mobile):
        """
        ---
        tags:
          - User
        summary: 手机短信登录 校验短信验证码
        description: 校验短信验证码，成功返回cookie设置保持登录，json中携带用户信息
        parameters:
          - name: mobile
            in: path
            description: 手机号
            required: true
            schema:
              type: string
          - name: sms_code
            in: query
            description: sms code
            required: true
            schema:
              type: string


        responses:
          '200':
            description: successful operation
            content:
              application/json:
                schema:
                  $ref: '#/components/schemas/OkModel'
          '400':
            description: error operation
            content:
              application/json:
                schema:
                  $ref: '#/components/schemas/ErrorModel'
        """

        sms_code = self.get_query_argument('sms_code', None)
        if sms_code is None:
            return self.write_error(errorCode.INVALID_ARGS, errorMsg.INVALID_ARGS_MSG, status_code=200)

        rows, res = await user.get_id_by_mobile(mobile)
        if rows == 0:
            return self.write_error(errorCode.MOBILE_NOT_EXIST, errorMsg.MOBILE_NOT_EXIST_MSG, status_code=200)

        cache_sms_code = await get_sms_code(mobile)
        self.logger.debug(cache_sms_code)
        self.logger.debug(sms_code)
        if cache_sms_code != sms_code:
            return self.write_error(errorCode.SMS_CODE_ERR, errorMsg.SMS_CODE_ERR_MSG, status_code=200)

        uid = res[0].get('id')
        session = self.save_to_session(uid)

        return self.write_json(kwargs=session)


class PasswordLogin(BaseHandler):
    def get_username_passwd(self):
        tmp = util.json_decode(self.request.body)
        return tmp.get("username", None), tmp.get("passwd", None)

    async def post(self):
        """
        ---
        tags:
          - User
        summary: 密码登录 提交用户信息
        description: 用户名，密码登录，成功返回cookie设置保持登录 ，json携带用户信息

        requestBody:
          description: Post object that needs to be added to the blog
          content:
            application/json:
                schema:
                  type: object
                  properties:
                    username:
                        type: string
                    passwd:
                        type: string

                  required:
                    - username
                    - passwd

                  required:
                    - sms_code
                    - username
                    - passwd
        responses:
          '200':
            description: successful operation
            content:
              application/json:
                schema:
                  $ref: '#/components/schemas/OkModel'
          '400':
            description: error operation
            content:
              application/json:
                schema:
                  $ref: '#/components/schemas/ErrorModel'
        """
        mobile, passwd = self.get_username_passwd()
        if not all((mobile, passwd)):
            return self.write_error(errorCode.INVALID_ARGS, errorMsg.INVALID_ARGS_MSG, status_code=200)

        rows, res = await user.get_id_by_mobile_password(mobile, passwd)
        if rows == 0:
            return self.write_error(errorCode.LOGIN_USER_OR_PASSWORD_ERR, errorMsg.LOGIN_USER_OR_PASSWORD_ERR_MSG,
                                    status_code=200)
        uid = res[0].get('id')
        session = self.save_to_session(uid)

        return self.write_json(kwargs=session)

    @BaseHandler.auth
    async def get(self):
        """
        ---
        tags:
          - User
        summary: 用户状态保持
        description: header携带user_token 校验通过返回用户信息，失败 返回400 错误 重新登录
        parameters:
        - in: header
          name: user_token
          schema:
            type: string
          required: true
          description: The user_token

        responses:
          '200':
            description: successful operation
            content:
              application/json:
                schema:
                  $ref: '#/components/schemas/OkModel'
          '400':
            description: error operation
            content:
              application/json:
                schema:
                  $ref: '#/components/schemas/ErrorModel'
        """
        return self.write_json()
        # rows_count, res = await user.get_id_by_id(self.current_user)
        #
        # if rows_count == 0:
        #     return self.write_error(errorCode.DB_ERR, errorMsg.DB_ERR_MSG, status_code=200)
        #
        # return self.write_json(res)

    @BaseHandler.auth
    def delete(self):
        """
        ---
        tags:
          - User
        summary: 用户退出
        description: header携带user_token
        parameters:
        - in: header
          name: user_token
          schema:
            type: string
          required: true
          description: The user_token

        responses:
          '200':
            description: successful operation
            content:
              application/json:
                schema:
                  $ref: '#/components/schemas/OkModel'
          '400':
            description: error operation
            content:
              application/json:
                schema:
                  $ref: '#/components/schemas/ErrorModel'
        """
        self.delete_from_session(self.session)
        return self.write_json()


class LoginPasswordModify(BaseHandler):
    @BaseHandler.auth
    async def put(self):
        """
        ---
        tags:
          - User
        summary: 修改密码
        description: 携带user_token 修改密码，成功后清除登录状态，删除headers中user_token
        parameters:
        - in: header
          name: user_token
          schema:
            type: string
          required: true
          description: The user_token

        requestBody:
          description:
          content:
            application/json:
                schema:
                  type: object
                  properties:
                    origin_password:
                        type: string
                    new_password1:
                        type: string
                    new_password2:
                        type: string

        responses:
          '200':
            description: successful operation
            content:
              application/json:
                schema:
                  $ref: '#/components/schemas/OkModel'

        """
        origin_password, new_password1, new_password2 = self.get_password_info()
        if not all((origin_password, new_password1, new_password2)):
            return self.write_error(errorCode.INVALID_ARGS, errorMsg.INVALID_ARGS_MSG, status_code=200)

        if new_password1 != new_password2:
            return self.write_error(errorCode.PASSWORD_DIFFERENT, errorMsg.PASSWORD_DIFFERENT_MSG, status_code=200)

        row_effect = await user.update_password(self.current_user, origin_password, new_password1)
        if row_effect == 0:
            return self.write_error(errorCode.PASSWORD_ERR, errorMsg.PASSWORD_ERR_MSG)
        self.delete_from_session()
        return self.write_json()

    def get_password_info(self):
        tmp = util.json_decode(self.request.body)
        origin_password = tmp.get('origin_password')
        new_password1 = tmp.get('new_password1')
        new_password2 = tmp.get('new_password2')
        return util.password_decrypt(origin_password), util.password_decrypt(new_password1), util.password_decrypt(
            new_password2)


class Password(SMSHandler):

    async def get(self):
        """

        ---
        tags:
          - User
        summary: 忘记密码，发送短信获取验证码
        description:
        parameters:
          - name: mobile
            in: query
            description: 手机号
            required: true
            schema:
              type: string
        responses:
          '200':
            description: successful operation
            content:
              application/json:
                schema:
                  $ref: '#/components/schemas/OkModel'
          '400':
            description: error operation
            content:
              application/json:
                schema:
                  $ref: '#/components/schemas/ErrorModel'
        """
        mobile = self.get_mobile_info()
        if not mobile:
            self.write_error(errorCode.INVALID_ARGS, errorMsg.INVALID_ARGS_MSG, status_code=200)

        rows, res = await user.get_user_by_mobile(mobile)
        print(rows, res)
        if rows > 0:
            return await self.send_msg(mobile, const.redis_prefix_mall_forget_password)
        return self.write_error(errorCode.MOBILE_NOT_EXIST, errorMsg.MOBILE_NOT_EXIST_MSG)

    async def post(self):
        """
        ---
        tags:
          - User
        summary: 提交手机短信验证码，修改用户密码
        description: 校验验证码成功返回token，提交密码时携带，有效期10分钟
        parameters:
          - name: mobile
            in: query
            description: 手机号
            required: true
            schema:
              type: string
          - name: sms_code
            in: query
            description: 短信验证码
            required: true
            schema:
              type: string

        responses:
          '200':
            description: successful operation
            content:
              application/json:
                schema:
                  $ref: '#/components/schemas/OkModel'

        """
        mobile = self.get_mobile_info()
        sms_code = self.get_sms_code_info()
        if not all((mobile, sms_code)):
            return self.write_error(errorCode.INVALID_ARGS, errorMsg.INVALID_ARGS_MSG, status_code=200)

        cache_sms_code = await get_sms_code(mobile, const.redis_prefix_mall_forget_password)
        if cache_sms_code is None:
            return self.write_error(errorCode.SMS_CODE_EXPIRED, errorMsg.SMS_CODE_EXPIRED_MSG, status_code=200)

        if sms_code != cache_sms_code:
            return self.write_error(errorCode.SMS_CODE_ERR, errorMsg.SMS_CODE_ERR_MSG, status_code=200)

        rows, res = await user.get_id_by_mobile(mobile)
        if rows == 0:
            return self.write_error(errorCode.MOBILE_NOT_EXIST, errorMsg.MOBILE_NOT_EXIST_MSG, status_code=200)

        unique_id = self.generate_uuid()
        hmac_key = self.generate_token(unique_id, mobile, sms_code)
        res = await user.set_ex_uuid(unique_id, sms_code, const.redis_reset_password_timeout)
        print(res, type(res))  # True bool
        token = "%s:%s" % (unique_id, hmac_key)
        ret = {'token': token}
        return self.write_json(ret)

    async def put(self):
        """
        ---
        tags:
          - User
        summary: 忘记密码，重新设置密码，修改成功不提供状态保持,清除状态保持
        description:

        requestBody:
          description: Post object that needs to be added to the blog
          content:
            application/json:
                schema:
                  type: object
                  properties:
                    mobile:
                        type: string
                    sms_code:
                        type: string
                    token:
                        type: string
                    passwd1:
                        type: string
                    passwd2:
                        type: string

        responses:
          '200':
            description: successful operation
            content:
              application/json:
                schema:
                  $ref: '#/components/schemas/OkModel'
          '400':
            description: error operation
            content:
              application/json:
                schema:
                  $ref: '#/components/schemas/ErrorModel'
        """
        mobile, sms_code, token, password1, password2 = self.get_password_info()
        print(mobile, sms_code, token, password1, password2)
        if not all((mobile, sms_code, token, password1, password2)):
            return self.write_error(errorCode.INVALID_ARGS, errorMsg.INVALID_ARGS_MSG, status_code=200)
        try:
            unique_id = token[:64]
            hmkey = token[65:]
        except:
            return self.write_error(errorCode.INVALID_ARGS, "token err0", status_code=200)

        if password1 != password2:
            return self.write_error(errorCode.PASSWORD_DIFFERENT, errorMsg.PASSWORD_DIFFERENT_MSG, status_code=200)

        if self.generate_token(unique_id, mobile, sms_code) != hmkey:
            return self.write_error(errorCode.INVALID_ARGS, "token err", status_code=200)

        chk_sms_code = await user.get_uuid_info(unique_id)
        self.logger.debug(chk_sms_code)
        if chk_sms_code is None:
            return self.write_error(errorCode.TOKEN_EXPIRED, errorMsg.TOKEN_EXPIRED)
        if chk_sms_code != sms_code:
            return self.write_error(errorCode.INVALID_ARGS, "token err1", status_code=200)

        row_effected = await user.update_password2(mobile, password2)
        # if row_effected == 0:
        #     return self.write_error(errorCode.DB_ERR, errorMsg.DB_ERR_MSG, status_code=200)
        self.delete_from_session()
        try:
            res = await user.del_uuid_info(unique_id)
            self.logger.debug(res)
        except Exception:
            self.logger.error(traceback.format_exc())
        return self.write_json()

    @staticmethod
    def generate_uuid():
        new_id = hashlib.sha256(config.SESSION_SECRET + uuid.uuid4().bytes)
        return new_id.hexdigest()

    @staticmethod
    def generate_token(uuid, mobile, sms_code):
        return hmac.new(config.SESSION_SECRET, (f"{uuid}:{mobile}:{sms_code}").encode(), hashlib.sha256).hexdigest()

    def get_mobile_info(self):
        return str(self.get_query_argument('mobile', None))

    def get_sms_code_info(self):
        return str(self.get_query_argument('sms_code', None))

    def get_token_info(self):
        return self.get_query_argument('token')

    def get_password_info(self):
        tmp = util.json_decode(self.request.body)
        mobile = tmp.get('mobile', None)
        sms_code = tmp.get('sms_code', None)
        token = tmp.get('token', None)
        password1 = tmp.get('passwd1', None)
        password2 = tmp.get('passwd2', None)
        return mobile, sms_code, token, util.password_decrypt(password1), util.password_decrypt(password2)


class UserInfoHandler(SMSHandler):
    @BaseHandler.auth
    async def get(self):
        """
        ---
        tags:
          - User
        summary: 获取用户个人信息
        description:
        parameters:
          - name: user_token
            in: header
            description:
            required: true
            schema:
              type: string

        responses:
          '200':
            description: successful operation
            content:
              application/json:
                schema:
                  $ref: '#/components/schemas/UserInfo'
          '400':
            description: error operation
            content:
              application/json:
                schema:
                  $ref: '#/components/schemas/ErrorModel'
        """
        row_count, res = await user.get_user_info(self.current_user)
        self.pack_img_url(res, 'icon')

        card,collect,history = await user.get_card_collect_history_count(6)
        res[0]['cards'] = card
        res[0]['collect'] = collect
        res[0]['trace'] = history
        self.logger.debug(res)
        return self.write_json(data=res)

    @BaseHandler.auth
    async def put(self):
        """
        ---
        tags:
          - User
        summary: 修改用户信息页 昵称
        description:
        parameters:
          - name: user_token
            in: header
            description:
            required: true
            schema:
              type: string
          - name: username
            in: query
            description:
            required: true
            schema:
              type: string

        responses:
          '200':
            description: successful operation
            content:
              application/json:
                schema:
                  $ref: '#/components/schemas/OkModel'
          '400':
            description: error operation
            content:
              application/json:
                schema:
                  $ref: '#/components/schemas/ErrorModel'
        """
        nickname = self.get_query_argument('nickname', None)
        print(self.request)
        if nickname is None:
            return self.write_error(errorCode.INVALID_ARGS, errorMsg.INVALID_ARGS_MSG, status_code=200)
        rows = await user.update_username(self.current_user, nickname)
        if rows == 0:
            return self.write_error(errorCode.DB_ERR, errorMsg.DB_ERR_MSG, status_code=200)
        return self.write_json()

    @BaseHandler.auth
    async def post(self):
        """
        ---
        tags:
          - User
        summary: 修改用户信息页 昵称 头像
        description:
        parameters:
          - name: user_token
            in: header
            description:
            required: true
            schema:
              type: string

        requestBody:
          description: Post object that needs to be added to the blog
          content:
            multipart/form-data:
                schema:
                  type: object
                  properties:
                    icon:
                      type: file
                    nickname:
                        type: string
        responses:
          '200':
            description: successful operation
            content:
              application/json:
                schema:
                  $ref: '#/components/schemas/OkModel'
          '400':
            description: error operation
            content:
              application/json:
                schema:
                  $ref: '#/components/schemas/ErrorModel'
        """
        if len(self.request.files) == 0:
            return self.write_error(errorCode.INVALID_ARGS, errorMsg.INVALID_ARGS_MSG, status_code=200)

        self.write_json()
        db_url = await self.save_img('icon')
        if db_url is None:
            self.logger.debug('icon is null')
            return
        await user.delete_user_info(self.current_user)
        row_count = await user.create_user_info(self.current_user, db_url)
        self.logger.debug(f"UPLOAD:{row_count}")
